// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace SnapObjects.Data
{
    internal class ModelEntryLazyReader<TModel> : IEnumerable<IModelEntry<TModel>>
    {
        private readonly bool _changedOnly;
        private readonly DataFormatSetting _setting;
        private readonly IEnumerable<TModel> _models;
        private readonly IModelAccessor<TModel> _accessor;
        private readonly IModelChangeTracker<TModel> _tracker;
        private readonly IDictionary<string, IPropertyMeta> _properties;
        private readonly Func<TModel, (ModelState modelState, bool changeReference)> _modelStateGetter;

        public ModelEntryLazyReader(
            IEnumerable<TModel> models,
            IModelAccessor<TModel> accessor,
            IModelChangeTracker<TModel> tracker,
            Func<TModel, (ModelState, bool)> modelStateGetter,
            bool changedOnly = false,
            DataFormatSetting setting = null)
        {
            FormatUtils.CheckSetting(setting, accessor.ModelMeta);

            _models = models;
            _tracker = tracker;
            _accessor = accessor;
            _changedOnly = changedOnly;
            _setting = setting ?? new DataFormatSetting();
            _modelStateGetter = modelStateGetter;

            _properties = accessor.ModelMeta.Properties
                .Where(x => this.FilterColumn(x.Index)).OrderBy(m => m.Index)
                .ToDictionary(x => x.Name, x => x, StringComparer.OrdinalIgnoreCase);
        }

        public IEnumerator<IModelEntry<TModel>> GetEnumerator()
        {
            var index = 0;
            foreach (var model in _models)
            {
                if (this.FilterRow(index++))
                {
                    var (state, changeReference) = _modelStateGetter(model);

                    if (!_changedOnly)
                    {
                        yield return this.GetModelEntry(model, state);
                    }
                    else
                    {
                        var isChanged = changeReference ||
                                        state == ModelState.New ||
                                        state == ModelState.NewModified ||
                                        state == ModelState.Modified ||
                                        state == ModelState.Deleted;
                        if (isChanged)
                        {
                            yield return this.GetModelEntry(model, state);
                        }
                    }
                }
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        private IModelEntry<TModel> GetModelEntry(TModel model, ModelState state)
        {
            return new LazyModelEntry<TModel>(model, state, _accessor, _tracker, _properties);
        }

        private bool FilterRow(int index)
        {
            return index >= _setting.StartRow && (_setting.EndRow < 0 || index <= _setting.EndRow);
        }

        private bool FilterColumn(int index)
        {
            return index >= _setting.StartProperty &&
                (_setting.EndProperty < 0 || index <= _setting.EndProperty);
        }
    }

    internal class LazyModelEntry<TModel> : IModelEntry<TModel>
    {
        private readonly TModel _model;
        private readonly IModelAccessor<TModel> _accessor;
        private readonly IModelChangeTracker<TModel> _tracker;
        private readonly IDictionary<string, IPropertyMeta> _properties;

        public LazyModelEntry(
            TModel model,
            ModelState state,
            IModelAccessor<TModel> accessor,
            IModelChangeTracker<TModel> tracker,
            IDictionary<string, IPropertyMeta> properties)
        {
            _model = model;
            _tracker = tracker;
            _accessor = accessor;
            _properties = properties;

            this.ModelState = state;
        }

        /// <summary>
        ///     Gets the tracked state of the data.
        /// </summary>
        public ModelState ModelState { get; }

        /// <summary>
        ///     Gets the property names of the model class.
        /// </summary>
        public string[] Properties => _properties.Keys.ToArray();

        /// <summary>
        ///     Gets the current value of the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The current value of the specified property.</returns>
        public object GetCurrentValue(string name)
        {
            //return _accessor.GetValue(_model, _properties[name].Index);
            return this.ConvertValue(_accessor.GetValue(_model, name));
        }

        /// <summary>
        ///     Gets the original value of the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The original value of the specified property.</returns>
        public object GetOriginalValue(string name)
        {
            _tracker.TryGetOriginalValue(_model, name, out var value);

            return this.ConvertValue(value);
        }

        /// <summary>
        ///     Gets the tracked state of the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The tracked state of the specified property.</returns>
        public PropertyState GetPropertyState(string name)
        {
            return _tracker.GetPropertyState(_model, _properties[name].Index);
        }

        /// <summary>
        ///     Occurs when the current value is changed.
        /// </summary>
        public event Action<string, object> CurrentValueChanged;

        /// <summary>
        ///     Sets the current value for the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <param name="value">The value set to the specified property.</param>
        public void SetCurrentValue(string name, object value)
        {
            //this.CurrentValueChanged(name, value);

            throw new NotImplementedException();
        }

        private object ConvertValue(object value)
        {
            if (value == null)
            {
                return value;
            }

            if (value is byte[] bytes)
            {
                return Convert.ToBase64String(bytes);
            }
            else
            {
                return value;
            }
        }
    }
}
